#include <app/aerApplication.hpp>
#include <app/aerWindow.hpp>
#include <app/aerEventHandler.hpp>

#include <device/aerProgram.hpp>
#include <device/aerDeviceBuffer.hpp>
#include <device/aerTexture.hpp>
#include <device/aerFrameBuffer.hpp>

#include <postprocess/aerPostProcess.hpp>
#include <postprocess/aerCustomEffect.hpp>//
#include <postprocess/aerBlurEffect.hpp>
#include <postprocess/aerBloomEffect.hpp>
#include <postprocess/aerDoFEffect.hpp>//


namespace aer {
  
class PostProcessApp : public aer::Application
{
  private:
    struct Pass 
    {
      Texture colorBuffer;
      Texture depthBuffer;
      FrameBuffer fbo;
    };
    
    struct Effects
    {
      CustomEffect custom;
      BlurEffect blur;
      BloomEffect bloom;
      DoFEffect dof;
    };
  
  
  private:
    Program m_program;
    DeviceBuffer m_vbuffer;
    
    Pass m_pass;
        
    PostProcess m_postProcess;
    Effects m_effects;
    
    float m_timeParam;
    
    
  public:
    PostProcessApp() : Application()
    {
      /// Window
      createWindow( Window::sDisplay_GL33_800x600, "PostProcessing example" );
            
      /// GLSL Program
      const char *vSource =
        "#version 330\n"
        "layout(location=0) in vec2 inPosition;"
        "void main() { gl_Position = vec4( inPosition, 0.0f, 1.0f);}";
      
      const char *fSource =
        "#version 330\n"
        "uniform vec3 uColor = vec3( 0.0f, 0.5f, 0.5f);"
        "void main() { gl_FragColor = vec4( uColor, 1.0f ); gl_FragColor.r = gl_FragCoord.x/800.;}";
      
      m_program.generate();
        m_program.addShaderSrc( Program::VERTEX_SHADER,   vSource );
        m_program.addShaderSrc( Program::FRAGMENT_SHADER, fSource );
      m_program.link();
      

      /// Mesh
      const float vertices[] = { -0.5f, -0.5f,   0.5f, -0.5f,   0.5f, 0.5f };
      
      m_vbuffer.generate();
      m_vbuffer.bind( DeviceBuffer::ARRAY );
        m_vbuffer.setData( vertices, sizeof(vertices) );
        glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0 );
      m_vbuffer.unbind();
      
      
      /// Pass (textures and frameBuffer)      
      m_pass.colorBuffer.generate();
      m_pass.colorBuffer.bind();      
        m_pass.colorBuffer.setFilters( Texture::NEAREST, Texture::NEAREST);
        m_pass.colorBuffer.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);      
      
      m_pass.depthBuffer.generate();
      m_pass.depthBuffer.bind();
        m_pass.depthBuffer.setFilters( Texture::NEAREST, Texture::NEAREST);
        m_pass.depthBuffer.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
              
      Texture::Unbind( Texture::TEXTURE_2D, 0u);
            
      m_pass.fbo.generate();
      
      
      /// PostProcess
      m_effects.blur.setKernelSize( 8 );
      
      //m_postProcess.addEffect( &m_effects.custom );
      m_postProcess.addEffect( &m_effects.bloom );
      //m_postProcess.addEffect( &m_effects.blur );
      //m_postProcess.addEffect( &m_effects.dof );
      m_postProcess.init();
      
      AER_CHECK( m_postProcess.linkInput( "Color", &m_pass.colorBuffer) );
      //AER_CHECK( m_postProcess.linkInput( "Depth", &m_pass.depthBuffer) );
      //AER_CHECK( m_postProcess.linkInput( "Time", &m_timeParam) );      
      m_postProcess.link();
    }
    
    ~PostProcessApp()
    {
      m_program.release();
      m_vbuffer.release();
      m_pass.colorBuffer.release();
      m_pass.depthBuffer.release();
      m_pass.fbo.release();
    }
    
    

    void resize(const U32 width, const U32 height)
    { 
      glViewport( 0.0f, 0.0f, width, height );
      
      m_pass.colorBuffer.bind();
      m_pass.colorBuffer.resize( width, height, GL_RGBA8);
      
      m_pass.depthBuffer.bind();
      m_pass.depthBuffer.resize( width, height, GL_DEPTH_COMPONENT24);
      
      Texture::Unbind( Texture::TEXTURE_2D, 0u);
            
      m_pass.fbo.bind();      
        m_pass.fbo.attachColorBuffer( &m_pass.colorBuffer, GL_COLOR_ATTACHMENT0);
        m_pass.fbo.attachSpecialBuffer( &m_pass.depthBuffer, GL_DEPTH_ATTACHMENT);
        
        AER_CHECK( FrameBuffer::CheckStatus() );      
      m_pass.fbo.unbind();
      
      m_postProcess.resize( width, height);
    }
    
    
    void updateFrame()
    {
      /// Exit condition
      if (EventHandler::IsKeyPressed( sf::Keyboard::Escape )) {
        quit();
      }
      
      // some postEffects needs a timer (provided externally).
      m_timeParam = m_timer.getRelativeTime() / 1000.f;
    }
    
    void drawFrame()
    {
      /// 1) Capture the scene in a framebuffer
      m_pass.fbo.begin();
      
      glEnable( GL_DEPTH_TEST );
      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
      
      m_program.bind();
        Vector3 color( 0.35f, 0.5f, 0.75f);
      
        m_program.setUniform( "uColor", color );
        glEnableVertexAttribArray( 0 );
        m_vbuffer.bind();
          glDrawArrays( GL_TRIANGLES, 0, m_vbuffer.getSize() );
        m_vbuffer.unbind();
        glDisableVertexAttribArray( 0 );        
      m_program.unbind();
      
      m_pass.fbo.end();
      
      
      /// 2) launch the postProcess passes
      m_postProcess.run();
      
      
      CHECKGLERROR();
    }    
};
  
} // aer



int main(int argc, char *argv[])
{
  aer::PostProcessApp app;
  
  app.run();
    
  return EXIT_SUCCESS;
}

